/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.desmond.squarecamera.slice;

import com.desmond.squarecamera.*;
import com.desmond.squarecamera.util.CameraConstants;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.animation.Animator;
import ohos.agp.components.*;
import ohos.agp.components.surfaceprovider.SurfaceProvider;
import ohos.agp.graphics.Surface;
import ohos.agp.graphics.SurfaceOps;
import ohos.agp.window.dialog.ToastDialog;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.camera.CameraKit;
import ohos.media.camera.device.*;
import ohos.media.camera.device.CameraAbility;
import ohos.media.camera.params.Metadata;
import ohos.media.camera.params.ParameterKey;
import ohos.media.camera.params.PropertyKey;
import ohos.media.image.ImageReceiver;
import ohos.media.image.common.ImageFormat;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;
import java.util.ArrayList;
import java.util.List;

import static ohos.media.camera.device.Camera.FrameConfigType.FRAME_CONFIG_PICTURE;
import static ohos.media.camera.device.Camera.FrameConfigType.FRAME_CONFIG_PREVIEW;

public class CameraAbilitySlice extends AbilitySlice {

    private Image mBtnCapture;
    private Component changeCameraFlashModeBtn;
    private Text autoFlashIcon;
    private Component mTopCover;
    private Component mBottomCover;

    public static final String TAG = CameraAbilitySlice.class.getSimpleName();
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD000F00, TAG);
    public static final String CAMERA_ID_KEY = "camera_id";
    public static final String CAMERA_FLASH_KEY = "flash_mode";
    public static final String IMAGE_INFO = "image_info";

    private static final int PICTURE_SIZE_MAX_WIDTH = 1280;
    private static final int PREVIEW_SIZE_MAX_WIDTH = 640;
    private static final int IMAGE_RCV_CAPACITY = 9;

    private int mCameraID;
    private String mCameraIdStr = "";
    private int mFlashMode;
    private Camera mCamera;
    private SquareLayout mPreviewView;
    private SurfaceProvider surfaceProvider;
    private Surface previewSurface;

    private boolean mIsSafeToTakePhoto = false;

    private ImageParameters mImageParameters;

    private EventHandler eventHandler = new EventHandler(EventRunner.current()) { };

    private ImageReceiver imageReceiver;

    private int defaultOrientation = 0; //原始相机的角度
    private int sensorOrientation = 0; //计算后需要旋转的角度

    private boolean isCameraCreating = true;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_camera);

        getWindow().setTransparent(true);

        initInfo();
        initView();
        initListener();
        initSurface();

        System.out.println("==== camera ability alice : on start...");
    }

    @Override
    public void onActive() {
        super.onActive();
        System.out.println("==== camera ability alice : on active...");
        if (mCamera == null && !isCameraCreating) {
            restartPreview();
            System.out.println("==== camera ability alice : on active restart...");
        }
    }

    private void initInfo() {
        if (CameraConstants.cameraId == CameraInfo.FacingType.CAMERA_FACING_BACK
                || CameraConstants.cameraId == CameraInfo.FacingType.CAMERA_FACING_FRONT
                || CameraConstants.cameraId == CameraInfo.FacingType.CAMERA_FACING_OTHERS) {
            mCameraID = CameraConstants.cameraId;
        } else {
            mCameraID = getBackCameraID();
        }
        mFlashMode = CameraSettingPreferences.getCameraFlashMode(getAbility());
        mImageParameters = new ImageParameters();
        mImageParameters.mIsPortrait = true;
    }

    private void initView() {
        changeCameraFlashModeBtn = findComponentById(ResourceTable.Id_flash);
        mBtnCapture = (Image) findComponentById(ResourceTable.Id_capture_image_button);
        mPreviewView = (SquareLayout) findComponentById(ResourceTable
                .Id_camera_preview_view);
        autoFlashIcon = (Text) findComponentById(ResourceTable.Id_auto_flash_icon);

        mTopCover = findComponentById(ResourceTable.Id_cover_top_view);
        mBottomCover = findComponentById(ResourceTable.Id_cover_bottom_view);

        mPreviewView.getComponentTreeObserver()
                .addWindowBoundListener(new ComponentTreeObserver.WindowBoundListener() {
            @Override
            public void onWindowBound() {

                mImageParameters.mPreviewWidth = mPreviewView.getWidth();
                mImageParameters.mPreviewHeight = mPreviewView.getHeight();
                mImageParameters.mCoverWidth = mImageParameters.mCoverHeight
                        = mImageParameters.calculateCoverWidthHeight();

                System.out.println("==== camera ability alice : on window bound...");
            }

            @Override
            public void onWindowUnbound() {
                mTopCover.getComponentTreeObserver().removeWindowBoundListener(this);
                System.out.println("==== camera ability alice : on window unbound...");
            }
        });

        setupFlashMode();
    }

    private void initListener() {
        mBtnCapture.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                takePicture();
            }
        });

        final Image swapCameraBtn = (Image) findComponentById(ResourceTable.Id_change_camera);
        swapCameraBtn.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                if (mCameraID == CameraInfo.FacingType.CAMERA_FACING_FRONT) {
                    mCameraID = getBackCameraID();
                } else {
                    mCameraID = getFrontCameraID();
                }
                CameraConstants.cameraId = mCameraID;
                swapCamera();
            }
        });

        changeCameraFlashModeBtn.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                if (mFlashMode == Metadata.FlashMode.FLASH_AUTO) {
                    mFlashMode = Metadata.FlashMode.FLASH_ALWAYS_OPEN;
                } else if (mFlashMode == Metadata.FlashMode.FLASH_ALWAYS_OPEN) {
                    mFlashMode = Metadata.FlashMode.FLASH_CLOSE;
                } else if (mFlashMode == Metadata.FlashMode.FLASH_CLOSE) {
                    mFlashMode = Metadata.FlashMode.FLASH_AUTO;
                }
                setupFlashMode();
                setupCamera();
            }
        });

        mBtnCapture.setTouchEventListener(new Component.TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                switch (touchEvent.getAction()) {
                    case TouchEvent.OTHER_POINT_DOWN:
                    case TouchEvent.PRIMARY_POINT_DOWN:
                        mBtnCapture.setPixelMap(ResourceTable
                                .Media_squarecamera__camera_snap_selected);
                        break;
                    case TouchEvent.PRIMARY_POINT_UP:
                    case TouchEvent.CANCEL:
                        mBtnCapture.setPixelMap(ResourceTable
                                .Media_squarecamera__camera_snap_unselected);
                        break;
                    default:
                        break;
                }
                return true;
            }
        });
    }

    private void resizeTopAndBtmCover(final Component topCover, final Component bottomCover) {

        ResizeAnimation topResize = new ResizeAnimation(topCover, mImageParameters);
        topResize.setDuration(800);
        topResize.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
        topResize.initUpdate();
        topResize.start();

        ResizeAnimation bottomResize = new ResizeAnimation(bottomCover, mImageParameters);
        bottomResize.setDuration(800);
        bottomResize.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
        bottomResize.initUpdate();
        bottomResize.start();
    }

    private void initSurface() {
        getWindow().setTransparent(true);
        ComponentContainer.LayoutConfig params = new ComponentContainer.LayoutConfig(
                ComponentContainer.LayoutConfig.MATCH_PARENT,
                ComponentContainer.LayoutConfig.MATCH_PARENT);
        surfaceProvider = new SurfaceProvider(this);
        surfaceProvider.setLayoutConfig(params);
        surfaceProvider.pinToZTop(false);
        surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack());
        mPreviewView.addComponent(surfaceProvider);
        mPreviewView.postLayout();
    }

    private class SurfaceCallBack implements SurfaceOps.Callback {
        @Override
        public void surfaceCreated(SurfaceOps callbackSurfaceOps) {

            callbackSurfaceOps.setFixedSize(200, 1000);
            eventHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    openCamera();
                }
            }, 200);
//            openCamera();

        }

        @Override
        public void surfaceChanged(SurfaceOps callbackSurfaceOps,
                                   int format,
                                   int width,
                                   int height) {


        }

        @Override
        public void surfaceDestroyed(SurfaceOps callbackSurfaceOps) {

        }
    }

    private void setupCamera() {
        if (mCamera == null) {
            return;
        }
    }

    private void restartPreview() {
        if (mCamera != null) {
            stopCameraPreview();
            releaseCamera();
        }
        openCamera();
    }

    private void openCamera() {

        isCameraCreating = true;

        CameraKit cameraKit = CameraKit.getInstance(getApplicationContext());
        String[] cameraList = cameraKit.getCameraIds();
        mCameraIdStr = "";
        for (String logicalCameraId : cameraList) {
            int faceType = cameraKit.getCameraInfo(logicalCameraId).getFacingType();
            switch (faceType) {

                case CameraInfo.FacingType.CAMERA_FACING_FRONT:
                    if (mCameraID == CameraInfo.FacingType.CAMERA_FACING_FRONT) {
                        mCameraIdStr = logicalCameraId;
                    }
                    break;
                case CameraInfo.FacingType.CAMERA_FACING_BACK:
                    if (mCameraID != CameraInfo.FacingType.CAMERA_FACING_FRONT) {
                        mCameraIdStr = logicalCameraId;
                    }
                    break;
                case CameraInfo.FacingType.CAMERA_FACING_OTHERS:
                default:
                    break;
            }
        }
        if (mCameraIdStr != null && !mCameraIdStr.isEmpty()) {
            CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
            cameraKit.createCamera(mCameraIdStr, cameraStateCallback, eventHandler);

            CameraAbility cameraAbility = cameraKit.getCameraAbility(mCameraIdStr);
            int[] flashModes = cameraAbility.getSupportedFlashMode();

            ArrayList<Integer> flashArr = new ArrayList<>();
            if (flashModes != null) {
                for (int flashMode : flashModes) {
                    flashArr.add(flashMode);
                }
            }
            if (flashModes != null && flashArr.contains(mFlashMode)) {
                changeCameraFlashModeBtn.setVisibility(Component.VISIBLE);
            } else {
                changeCameraFlashModeBtn.setVisibility(Component.INVISIBLE);
            }

            defaultOrientation = cameraAbility.getPropertyValue(PropertyKey.SENSOR_ORIENTATION);

            determineDisplayOrientation();

            List<Size> supportedSizes = cameraAbility.getSupportedSizes(ImageFormat.JPEG);
            Size bestSize = determineBestSize(supportedSizes);

            imageReceiver = ImageReceiver.create(bestSize.width,
                    bestSize.height,
                    ImageFormat.JPEG,
                    IMAGE_RCV_CAPACITY);
            imageReceiver.setImageArrivalListener(this::onPictureTaken);

        }
    }

    private void onPictureTaken(ImageReceiver receiver) {

        determineDisplayOrientation();

        ohos.media.image.Image image = receiver.readNextImage();
        ohos.media.image.Image.Component component = image
                .getComponent(ImageFormat.ComponentType.JPEG);
        byte[] bytes = new byte[component.remaining()];
        component.read(bytes);

        Intent intent = new Intent();
        intent.setParam(EditSavePhotoSlice.BITMAP_KEY, bytes);
        intent.setParam(EditSavePhotoSlice.ROTATION_KEY, sensorOrientation);
        present(new EditSavePhotoSlice(), intent);

    }

    private void showTips(Context context, String msg) {
        getUITaskDispatcher().asyncDispatch(() -> new ToastDialog(context).setText(msg).show());
    }

    private class CameraStateCallbackImpl extends CameraStateCallback {
        CameraStateCallbackImpl() {
        }

        @Override
        public void onCreated(Camera camera) {


            mCamera = camera;

            previewSurface = surfaceProvider.getSurfaceOps().get().getSurface();
            if (previewSurface == null) {
                HiLog.error(LABEL_LOG, "%{public}s",
                        "Create camera filed, preview surface is null");
                return;
            }
            CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder();
            cameraConfigBuilder.addSurface(previewSurface);
            cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
            try {
                camera.configure(cameraConfigBuilder.build());
            } catch (Exception e) {
                e.printStackTrace();
            }

            resizeTopAndBtmCover(mTopCover, mBottomCover);
//            mPreviewView.setCamera(camera, mCameraID, previewSurface);
            setCameraFocusReady(true);

            isCameraCreating = false;
        }

        @Override
        public void onConfigured(Camera camera) {

            FrameConfig.Builder framePreviewConfigBuilder = camera
                    .getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
            framePreviewConfigBuilder.addSurface(previewSurface);
            try {
                camera.triggerLoopingCapture(framePreviewConfigBuilder.build());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onCreateFailed(String cameraId, int errorCode) {
            isCameraCreating = false;
            super.onCreateFailed(cameraId, errorCode);
        }
    }

    private void swapCamera() {

        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }

        openCamera();
    }

    private int getBackCameraID() {
        return CameraInfo.FacingType.CAMERA_FACING_BACK;
    }

    private int getFrontCameraID() {
        return CameraInfo.FacingType.CAMERA_FACING_FRONT;
    }

    private void setupFlashMode() {
        if (mFlashMode == Metadata.FlashMode.FLASH_AUTO) {
            autoFlashIcon.setText("Auto");
        } else if (mFlashMode == Metadata.FlashMode.FLASH_ALWAYS_OPEN) {
            autoFlashIcon.setText("On");
        }  else if (mFlashMode == Metadata.FlashMode.FLASH_CLOSE) {
            autoFlashIcon.setText("Off");
        }
    }

    private void takePicture() {
        if (mCamera == null || imageReceiver == null) {
            return;
        }
        FrameConfig.Builder framePictureConfigBuilder = mCamera.getFrameConfigBuilder(FRAME_CONFIG_PICTURE);
        framePictureConfigBuilder.setFlashMode(mFlashMode);
        framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface());

        if (mCameraID == CameraInfo.FacingType.CAMERA_FACING_FRONT) {
            //判断是否为前置，如果为前置就设置镜像
            framePictureConfigBuilder.setParameter(ParameterKey.IMAGE_MIRROR, true);
        }

        FrameConfig pictureFrameConfig = framePictureConfigBuilder.build();
        mCamera.triggerSingleCapture(pictureFrameConfig);
    }

    private void setCameraFocusReady(final boolean isFocusReady) {
        if (this.mPreviewView != null) {
//            mPreviewView.setIsFocusReady(isFocusReady);
        }
    }

    private void stopCameraPreview() {
        setCameraFocusReady(false);
        mCamera.stopLoopingCapture();
//        mPreviewView.setCamera(null, 0, null);
    }

    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }

        if (imageReceiver != null) {
            imageReceiver.release();
            imageReceiver = null;
        }
    }

    private void determineDisplayOrientation() {
        int rotation = DisplayManager.getInstance().getDefaultDisplay(this).get().getRotation();

        int degrees = 0;
        switch (rotation) {
            case 0:
                degrees = 0;
                break;
            case 1:
                degrees = 90;
                break;
            case 2:
                degrees = 180;
                break;
            case 3:
                degrees = 270;
                break;
            default:
                break;
        }

        if (mCameraID == CameraInfo.FacingType.CAMERA_FACING_FRONT) {
            //前置
            sensorOrientation = (defaultOrientation + degrees) % 360;
            sensorOrientation = (360 - sensorOrientation) % 360;
        } else {
            //后置
            sensorOrientation = (defaultOrientation - degrees + 360) % 360;
        }

    }

    private Size determineBestSize(List<Size> sizes) {

        Size bestSize = null;
        Size size;
        int numOfSizes = sizes.size();
        for (int i = 0; i < numOfSizes; i++) {
            size = sizes.get(i);
            boolean isDesireRatio = size.width == size.height;
            boolean isBetterSize = (bestSize == null) || size.width > bestSize.width;

            if (isDesireRatio && isBetterSize) {
                bestSize = size;
            }

        }

        if (bestSize == null) {
            return sizes.get(sizes.size() - 1);
        }

        return bestSize;
    }

    @Override
    protected void onInactive() {
        releaseCamera();
        CameraSettingPreferences.saveCameraFlashMode(getAbility(), mFlashMode);
        super.onInactive();
    }


}
